"""
Validation Utilities
===================

Input validation functions for API requests.
Provides validation for user registration, login, and other operations.
"""

import re
from typing import Dict, Any, Tuple, List
from datetime import datetime

class ValidationError(Exception):
    """Custom validation error exception."""
    pass

def validate_email(email: str) -> Tuple[bool, str]:
    """
    Validate email address format.
    
    Args:
        email: Email address to validate
    
    Returns:
        Tuple of (is_valid, error_message)
    """
    if not email:
        return False, "Email is required"
    
    if not isinstance(email, str):
        return False, "Email must be a string"
    
    email = email.strip()
    
    if len(email) > 254:  # RFC 5321 limit
        return False, "Email address is too long"
    
    # RFC 5322 compliant regex
    email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    
    if not re.match(email_pattern, email):
        return False, "Invalid email format"
    
    return True, ""

def validate_username(username: str) -> Tuple[bool, str]:
    """
    Validate username format.
    
    Args:
        username: Username to validate
    
    Returns:
        Tuple of (is_valid, error_message)
    """
    if not username:
        return False, "Username is required"
    
    if not isinstance(username, str):
        return False, "Username must be a string"
    
    username = username.strip()
    
    if len(username) < 3:
        return False, "Username must be at least 3 characters long"
    
    if len(username) > 80:
        return False, "Username must not exceed 80 characters"
    
    # Username pattern: alphanumeric, underscore, and hyphen only
    username_pattern = r'^[a-zA-Z0-9_-]+$'
    
    if not re.match(username_pattern, username):
        return False, "Username can only contain letters, numbers, underscores, and hyphens"
    
    # Check for consecutive special characters
    if '__' in username or '--' in username or '_-' in username or '-_' in username:
        return False, "Username cannot contain consecutive special characters"
    
    return True, ""

def validate_password(password: str) -> Tuple[bool, str]:
    """
    Validate password strength.
    
    Args:
        password: Password to validate
    
    Returns:
        Tuple of (is_valid, error_message)
    """
    if not password:
        return False, "Password is required"
    
    if not isinstance(password, str):
        return False, "Password must be a string"
    
    errors = []
    
    # Length requirements
    if len(password) < 8:
        errors.append("at least 8 characters long")
    
    if len(password) > 128:
        errors.append("not exceed 128 characters")
    
    # Character requirements
    if not any(c.islower() for c in password):
        errors.append("at least one lowercase letter")
    
    if not any(c.isupper() for c in password):
        errors.append("at least one uppercase letter")
    
    if not any(c.isdigit() for c in password):
        errors.append("at least one digit")
    
    if not any(c in '!@#$%^&*(),.?":{}|<>' for c in password):
        errors.append("at least one special character (!@#$%^&*(),.?\":{}|<>)")
    
    # Check for common weak passwords
    weak_passwords = {
        'password', '123456', '123456789', 'qwerty', 'abc123',
        'password123', 'admin', 'letmein', 'welcome', 'monkey',
        'dragon', 'master', 'hello', 'login', 'test'
    }
    
    if password.lower() in weak_passwords:
        errors.append("not be a common password")
    
    # Check for repeated characters
    if any(c * 3 in password for c in password):
        errors.append("not contain repeated characters more than twice")
    
    # Check for sequential characters
    sequential_patterns = [
        'abcdefghijklmnopqrstuvwxyz',
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
        '0123456789'
    ]
    
    for pattern in sequential_patterns:
        for i in range(len(pattern) - 2):
            if pattern[i:i+3] in password:
                errors.append("not contain sequential characters")
                break
    
    if errors:
        error_message = "Password must be " + ", ".join(errors[:-1])
        if len(errors) > 1:
            error_message += " and " + errors[-1]
        else:
            error_message = "Password " + errors[0]
        return False, error_message
    
    return True, ""

def validate_name(name: str, field_name: str = "Name") -> Tuple[bool, str]:
    """
    Validate name fields (first_name, last_name).
    
    Args:
        name: Name to validate
        field_name: Field name for error messages
    
    Returns:
        Tuple of (is_valid, error_message)
    """
    if not name:
        return True, ""  # Names are optional
    
    if not isinstance(name, str):
        return False, f"{field_name} must be a string"
    
    name = name.strip()
    
    if len(name) > 50:
        return False, f"{field_name} must not exceed 50 characters"
    
    # Name pattern: letters, spaces, hyphens, and apostrophes only
    name_pattern = r"^[a-zA-Z\s\-\']+$"
    
    if not re.match(name_pattern, name):
        return False, f"{field_name} can only contain letters, spaces, hyphens, and apostrophes"
    
    # Check for consecutive special characters
    if '--' in name or "''" in name or '  ' in name:
        return False, f"{field_name} cannot contain consecutive special characters"
    
    return True, ""

def validate_registration(data: Dict[str, Any]) -> Tuple[bool, Dict[str, List[str]]]:
    """
    Validate user registration data.
    
    Args:
        data: Registration data dictionary
    
    Returns:
        Tuple of (is_valid, errors_dict)
    """
    errors = {}
    
    # Required fields
    required_fields = ['username', 'email', 'password']
    for field in required_fields:
        if field not in data or not data[field]:
            if field not in errors:
                errors[field] = []
            errors[field].append(f"{field.replace('_', ' ').title()} is required")
    
    # Validate username
    if 'username' in data and data['username']:
        is_valid, error = validate_username(data['username'])
        if not is_valid:
            if 'username' not in errors:
                errors['username'] = []
            errors['username'].append(error)
    
    # Validate email
    if 'email' in data and data['email']:
        is_valid, error = validate_email(data['email'])
        if not is_valid:
            if 'email' not in errors:
                errors['email'] = []
            errors['email'].append(error)
    
    # Validate password
    if 'password' in data and data['password']:
        is_valid, error = validate_password(data['password'])
        if not is_valid:
            if 'password' not in errors:
                errors['password'] = []
            errors['password'].append(error)
    
    # Validate first_name (optional)
    if 'first_name' in data and data['first_name']:
        is_valid, error = validate_name(data['first_name'], 'First name')
        if not is_valid:
            if 'first_name' not in errors:
                errors['first_name'] = []
            errors['first_name'].append(error)
    
    # Validate last_name (optional)
    if 'last_name' in data and data['last_name']:
        is_valid, error = validate_name(data['last_name'], 'Last name')
        if not is_valid:
            if 'last_name' not in errors:
                errors['last_name'] = []
            errors['last_name'].append(error)
    
    return len(errors) == 0, errors

def validate_login(data: Dict[str, Any]) -> Tuple[bool, Dict[str, List[str]]]:
    """
    Validate user login data.
    
    Args:
        data: Login data dictionary
    
    Returns:
        Tuple of (is_valid, errors_dict)
    """
    errors = {}
    
    # Required fields
    if 'username_or_email' not in data or not data['username_or_email']:
        errors['username_or_email'] = ['Username or email is required']
    
    if 'password' not in data or not data['password']:
        errors['password'] = ['Password is required']
    
    # Validate username/email format
    if 'username_or_email' in data and data['username_or_email']:
        username_or_email = data['username_or_email'].strip()
        
        # Check if it's an email
        if '@' in username_or_email:
            is_valid, error = validate_email(username_or_email)
            if not is_valid:
                errors['username_or_email'] = ['Invalid email format']
        else:
            # Treat as username
            is_valid, error = validate_username(username_or_email)
            if not is_valid:
                errors['username_or_email'] = [error]
    
    # Validate password format (basic check)
    if 'password' in data and data['password']:
        password = data['password']
        if not isinstance(password, str) or len(password) < 1:
            errors['password'] = ['Invalid password format']
    
    return len(errors) == 0, errors

def validate_password_reset(data: Dict[str, Any]) -> Tuple[bool, Dict[str, List[str]]]:
    """
    Validate password reset data.
    
    Args:
        data: Password reset data dictionary
    
    Returns:
        Tuple of (is_valid, errors_dict)
    """
    errors = {}
    
    # Required fields
    if 'token' not in data or not data['token']:
        errors['token'] = ['Reset token is required']
    
    if 'new_password' not in data or not data['new_password']:
        errors['new_password'] = ['New password is required']
    
    # Validate new password
    if 'new_password' in data and data['new_password']:
        is_valid, error = validate_password(data['new_password'])
        if not is_valid:
            if 'new_password' not in errors:
                errors['new_password'] = []
            errors['new_password'].append(error)
    
    return len(errors) == 0, errors

def validate_email_request(data: Dict[str, Any]) -> Tuple[bool, Dict[str, List[str]]]:
    """
    Validate email request data.
    
    Args:
        data: Email data dictionary
    
    Returns:
        Tuple of (is_valid, errors_dict)
    """
    errors = {}
    
    if 'email' not in data or not data['email']:
        errors['email'] = ['Email is required']
    elif not isinstance(data['email'], str):
        errors['email'] = ['Email must be a string']
    else:
        is_valid, error = validate_email(data['email'])
        if not is_valid:
            errors['email'] = [error]
    
    return len(errors) == 0, errors

def sanitize_input(data: Dict[str, Any]) -> Dict[str, Any]:
    """
    Sanitize input data by removing extra whitespace.
    
    Args:
        data: Input data dictionary
    
    Returns:
        Sanitized data dictionary
    """
    sanitized = {}
    
    for key, value in data.items():
        if isinstance(value, str):
            sanitized[key] = value.strip()
        else:
            sanitized[key] = value
    
    return sanitized